package com.yindi.sftp.utils;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import com.yindi.sftp.config.Parameters;
import lombok.extern.slf4j.Slf4j;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.ConnectionException;
import net.schmizz.sshj.connection.channel.direct.Session;
import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.RenameFlags;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.transport.TransportException;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;

import java.io.*;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static com.yindi.sftp.config.Parameters.threadPool;

/**
 * https://github.com/hierynomus/sshj
 *
 * @author qing-feng.zhao
 * @Sees https://stackoverflow.com/questions/14617/how-to-retrieve-a-file-from-a-server-via-sftp
 */
@Slf4j
public final class SmartSshUtils {

    public static SFTPClient create(String hostName,
                                    String port,
                                    String username,
                                    String password,
                                    String timeout
    ) {
        SSHClient ssh = new SSHClient();
        SFTPClient sftpClient = null;
        try {
            ssh.addHostKeyVerifier(new PromiscuousVerifier());
            ssh.setConnectTimeout(Integer.parseInt(timeout) * 1000);
            ssh.connect(hostName, Integer.parseInt(port));
            ssh.authPassword(username, password);
            sftpClient = ssh.newSFTPClient();
            return sftpClient;
        } catch (IOException e) {
            log.error("尝试连接服务器：{} 超时，异常信息 {}", hostName, e.getMessage(), e);
        }
        return null;
    }

    public static boolean chickPathIsFile(RemoteResourceInfo path) {
        return path.isDirectory();
    }

    /**
     * 获取远程服务目录
     */
    public static List<RemoteResourceInfo> getRemoteFiles(
            SFTPClient sftpClient,
            String path
    ) {
        try {
            return sftpClient.ls(path);
        } catch (IOException e) {
            log.error("目录不存在: {}", path);
        }
        return new ArrayList<>();
    }

    public static void downLoadFileBySsh(Parameters.Dir dir, String tmpDir) {
        try (SFTPClient sftpClient = create(
                dir.getCommon().getServerIp(),
                dir.getCommon().getServerPort(),
                dir.getCommon().getUserId(),
                dir.getCommon().getPasswd(),
                Parameters.common.getSockTimeout())
        ) {
            if (sftpClient != null) {
                log.info("id: {} 正在下载中", dir.getId());
                long startTime = System.currentTimeMillis();
                List<CompletableFuture<Void>> list = new ArrayList<>();
                remoteOperation(dir, tmpDir + "\\" + dir.getId(), "", sftpClient, list);
                if(list.isEmpty()){
                    return;
                }
                //等待线程全部完成
                CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(list.stream().toArray(CompletableFuture[]::new));
                voidCompletableFuture.get();
                //将临时目录转化为实际使用目录
                File file = new File(tmpDir + "\\" + dir.getId());
                FileUtil.copyContent(file, new File(dir.getDstDir()), true);
                log.info("目录转化: {} -> {}", file.getPath(), dir.getDstDir());
                //拷贝目录或者文件
                if (Objects.nonNull(dir.getBakDirs()) && !dir.getDstDir().isEmpty()) {
                    dir.getBakDirs().forEach(bakDir -> {
                        log.info("备份文件：{} -> {}", file.getPath(), bakDir);
                        FileUtil.copyContent(file, new File(bakDir), true);
                    });
                }
                FileUtil.del(file);
                if ("1".equals(dir.getDeleteRemote())) {
                    log.info("删除目录: {}", dir.getSrcDir());
                    deleteDir(dir.getSrcDir(), sftpClient);
                }
                //删除临时目录
                FileUtil.del(file);
                long endTime = System.currentTimeMillis();
                log.info("id: {} 下载完成 耗时: {}秒", dir.getId(), (endTime - startTime) / 1000);
            }
        } catch (IOException | InterruptedException | ExecutionException e) {
            log.info("下载失败：{}", e.getMessage());
            e.printStackTrace();
        }
    }

    public static void upLoadFileBySsh(Parameters.Dir dir, String tmpDir) {
        File file = new File(dir.getSrcDir());
        //判断来源目录是否含有东西 或者压根就不存在
        if (file.exists() && ((file.isDirectory() && Objects.requireNonNull(file.list()).length > 0) || !file.isDirectory())) {
            try (SFTPClient sftpClient = create(
                    dir.getCommon().getServerIp(),
                    dir.getCommon().getServerPort(),
                    dir.getCommon().getUserId(),
                    dir.getCommon().getPasswd(),
                    Parameters.common.getSockTimeout())
            ) {
                if (sftpClient != null) {
                    log.info("id: {} 正在上传中", dir.getId());
                    long startTime = System.currentTimeMillis();

                    List<CompletableFuture<Void>> list = new ArrayList<>();
                    fileOperation(dir, file, sftpClient, list, tmpDir);
                    //等待线程全部完成
                    CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(list.stream().toArray(CompletableFuture[]::new));
                    voidCompletableFuture.get();
                    //临时目录转化为实际目录
                    deleteTempDir(dir.getDstDir(), sftpClient);
                    sftpClient.rename(tmpDir + "/" + dir.getId(), dir.getDstDir(), EnumSet.of(RenameFlags.OVERWRITE));
                    //拷贝目录或者文件
                    if (Objects.nonNull(dir.getBakDirs()) && !dir.getDstDir().isEmpty()) {
                        dir.getBakDirs().forEach(bakDir -> {
                            log.info("备份文件：{} -> {}", file.getPath(), bakDir);
                            FileUtil.copyContent(file, new File(bakDir), true);
                        });
                    }
                    //删除原来的文件
                    if ("1".equals(dir.getDeleteRemote())) {
                        log.info("清空原来的文件：{}", file.getName());
                        try {
                            FileUtil.clean(file);
                        } catch (IORuntimeException e) {
                            log.error("文件正在使用中无法删除：{}", file.getPath());
                        }
                    }
                    long endTime = System.currentTimeMillis();
                    log.info("id: {} 上传完成 耗时: {}秒", dir.getId(), (endTime - startTime) / 1000);
                }
            } catch (IOException | InterruptedException | ExecutionException e) {
                log.info("上传失败：{}", e.getMessage());
                e.printStackTrace();
            }
        } else {
            log.info("id: {} 没有需要上传的文件...", dir.getId());
        }
    }

    public static void deleteTempDir(String path, SFTPClient sftpClient) {
        // 列出目录中的文件和子目录
        try {
            sftpClient.ls(path).forEach(fileEntry -> {
                String fileName = fileEntry.getName();
                String filePath = path + "/" + fileName;

                if (fileEntry.isDirectory()) {
                    // 如果是子目录，递归删除
                    deleteTempDir(filePath, sftpClient);
                } else {
                    // 如果是文件，删除文件
                    try {
                        sftpClient.rm(filePath);
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("文件删除失败：{}", filePath);
                    }
                }
            });
            sftpClient.rmdir(path);
        } catch (IOException e) {
            e.printStackTrace();
            log.error("目录删除失败： {}", path);
        }
    }

    public static void deleteDir(String path, SFTPClient sftpClient) {
        // 列出目录中的文件和子目录
        try {
            sftpClient.ls(path).forEach(fileEntry -> {
                String fileName = fileEntry.getName();
                String filePath = path + "/" + fileName;

                if (fileEntry.isDirectory()) {
                    // 如果是子目录，递归删除
                    deleteDir(filePath, sftpClient);
                } else {
                    // 如果是文件，删除文件
                    try {
                        sftpClient.rm(filePath);
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("文件删除失败：{}", filePath);
                    }
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
            log.error("目录删除失败： {}", path);
        }
    }

    public static void remoteOperation(Parameters.Dir dir, String tmpPath, String path, SFTPClient sftpClient, List<CompletableFuture<Void>> list) {
        //解析目录
        List<RemoteResourceInfo> remoteFiles = getRemoteFiles(sftpClient,
                dir.getSrcDir() + path);
        if (Objects.nonNull(remoteFiles)) {
            if (remoteFiles.isEmpty()) {
                return;
            }
            for (int i = 0; i < remoteFiles.size(); i++) {
                if (chickPathIsFile(remoteFiles.get(i))) {
                    String fileName = remoteFiles.get(i).getName();
                    //如果是目录就继续遍历
                    remoteOperation(dir, tmpPath, path + "/" + fileName, sftpClient, list);
                } else {
                    //下载
                    int finalI = i;
                    CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
                        try {
                            String fileName = remoteFiles.get(finalI).getName();
                            File directory = new File(tmpPath + path);
                            if (!directory.exists()) {
                                boolean created = directory.mkdirs(); // 如果目录不存在，则创建该目录及其所有父级目录
                                if (created) {
                                    log.info("创建目录成功： {}", dir.getDstDir() + path);
                                }
                            }
                            log.info("文件路径：{}, {}", dir.getSrcDir() + path + "/" + fileName, tmpPath + path + "\\" + fileName);
                            sftpClient.get(dir.getSrcDir() + path + "/" + fileName, tmpPath + path + "\\" + fileName);
                            log.info("文件下载成功: {}", dir.getSrcDir() + path + "/" + fileName);
                        } catch (IOException e) {
                            e.printStackTrace();
                            log.error("文件下载失败: {}", dir.getSrcDir());
                        }
                    }, threadPool);
                    list.add(voidCompletableFuture);
                }
            }
        }
    }

    public static void fileOperation(Parameters.Dir dir, File file, SFTPClient sftpClient, List<CompletableFuture<Void>> list, String tmpDir) {
        try {
            //目录
            if (file.isDirectory()) {
                // todo 创建该目录 待优化  找到最深的目录一次创建 不需要多次IO
                String path = file.getPath();
                String rep = path.replace(dir.getSrcDir(), "");
                String replace = rep.replace("\\", "/");
                // 创建临时目录
                sftpClient.mkdir(tmpDir + "/" + dir.getId() + replace);

                File[] files = file.listFiles();
                if (Objects.nonNull(files)) {
                    for (File f : files) {
                        fileOperation(dir, f, sftpClient, list, tmpDir);
                    }
                }
            } else {
                //文件
                CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
                    try {
                        String path = file.getPath();
                        String rep = path.replace(dir.getSrcDir(), "");
                        String replace = rep.replace("\\", "/");
                        log.info("文件路径：{}, {}", path, tmpDir + "/" + dir.getId() + replace);
                        sftpClient.put(file.getPath(), tmpDir + "/" + dir.getId() + replace);
                        log.info("文件上传成功: {}", path);
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("文件上传失败: {}", file.getName());
                    }
                }, threadPool);
                list.add(voidCompletableFuture);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 静态工具类应该禁用构造方法
     */
    private SmartSshUtils() {
    }
}
